use std::time::Duration;

use anyhow::Context;
use nix::unistd::Pid;
use tokio::{
    io::{AsyncBufReadExt, BufReader},
    process::{Child, Command},
    time::timeout,
};

pub async fn sigint_and_wait(child: &mut Child) {
    if let Some(pid) = child.id() {
        if let Err(e) = nix::sys::signal::kill(Pid::from_raw(pid as i32), nix::sys::signal::SIGINT)
        {
            log::error!("Failed to send SIGINT: {:?}, killing", e);
            if let Err(e) = child.kill().await {
                log::error!("Failed to kill: {:?}", e);
            }
        }
    }

    log::info!("Waiting for the process to exit...");
    match timeout(Duration::from_secs(60), child.wait()).await {
        Ok(Ok(x)) => {
            log::info!("Exit code: {:?}", x.code());
        }
        Ok(Err(e)) => {
            log::error!("Failed to wait: {:?}", e);
        }
        Err(_) => {
            log::error!("Timed out waiting for process to exit, ignoring");
        }
    }
}

pub fn start_forwarding_log(child: &mut Child, target: &str) {
    if let Some(stdout) = child.stdout.take() {
        let target = format!("{}::stdout", target);

        tokio::spawn(async move {
            let mut lines = BufReader::new(stdout).lines();
            log::info!(target: &target, "=== BEGIN ===");

            loop {
                match lines.next_line().await {
                    Ok(Some(line)) => {
                        log::info!(target: &target, "{}", line);
                    }
                    Ok(None) => {
                        break;
                    }
                    Err(e) => {
                        log::error!(target: &target, "Failed to forward: {:?}", e);
                        break;
                    }
                }
            }

            log::info!(target: &target, "=== END ===");
        });
    }
    if let Some(stderr) = child.stderr.take() {
        let target = format!("{}::stderr", target);

        tokio::spawn(async move {
            let mut lines = BufReader::new(stderr).lines();
            log::info!(target: &target, "=== BEGIN ===");

            loop {
                match lines.next_line().await {
                    Ok(Some(line)) => {
                        log::info!(target: &target, "{}", line);
                    }
                    Ok(None) => {
                        break;
                    }
                    Err(e) => {
                        log::error!(target: &target, "Failed to forward: {:?}", e);
                        break;
                    }
                }
            }

            log::info!(target: &target, "=== END ===");
        });
    }
}

pub async fn run_command(cmd: &mut Command, timeout: Duration) -> anyhow::Result<()> {
    cmd.kill_on_drop(true);

    let output = tokio::time::timeout(timeout, cmd.output())
        .await
        .with_context(|| format!("Command timed out after {} seconds", timeout.as_secs()))?
        .context("Failed to spawn command")?;

    if !output.status.success() {
        log::error!(
            "Failed to execute command, stdout: {:?}, stderr: {:?}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );
        anyhow::bail!("Failed to run command, non-zero exit code");
    }

    Ok(())
}
